#include "vm.h"

struct cpu{
       int pc;
       int bp;
       int sp;
       struct instruction ir;
       int stack[MAX_STACK_LENGTH];
};

struct cpu cpuMain;

void fetch();
void execute();
void opr();
void printStack(int n);
int max(int a, int b);
int base(int l, int basepointer);
char *opToString(int op);

void vm(void){
	//variable declarations
	int i, end, line;

	//initializations
	cpuMain.pc = 0;
    cpuMain.bp = 1;
    cpuMain.sp = 0;
    for (i=0; i<MAX_STACK_LENGTH; i++)
    	cpuMain.stack[i] = 0;

    //First, we need to print the passed-in code to the screen in assembly language.
    i = 0;
    while(code[i].op != -1){
       printf("%d %s %d %d\n", i, opToString(code[i].op), code[i].l, code[i].m);
       i++;
    }

    //Print initial lines
    printf("\t\tpc\tbp\tsp\tstack\n");
    printf("Initial values\t%d\t%d\t%d\t", cpuMain.pc, cpuMain.bp, cpuMain.sp);
    printStack(5);
    end = 0;

    /*Instructions for IP*/
    while(end != 1){
       line = cpuMain.pc;
       fetch();
       if (code[cpuMain.pc].op == -1)
          end = 1;
       execute();
       printf("%d %s %d %d\t%d\t%d\t%d\t", line, opToString(code[line].op), cpuMain.ir.l,
              cpuMain.ir.m, cpuMain.pc, cpuMain.bp, cpuMain.sp);
       printStack(max(cpuMain.sp, 5));
    }//end of while loop
    printf("end of program\n");
}

void fetch(){
     cpuMain.ir.op = code[cpuMain.pc].op;
     cpuMain.ir.l = code[cpuMain.pc].l;
     cpuMain.ir.m = code[cpuMain.pc].m;
     cpuMain.pc++;
}//end of fetch cycle

void execute(){
     switch (cpuMain.ir.op){
            case LIT:
                 cpuMain.sp++;
                 cpuMain.stack[cpuMain.sp-1] = cpuMain.ir.m;
                 break;
            case OPR:
                 opr();
                 break;
            case LOD:
                 cpuMain.sp++;
                 cpuMain.stack[cpuMain.sp-1] = cpuMain.stack[base(cpuMain.ir.l, cpuMain.bp) + cpuMain.ir.m];
                 break;
            case STO:
                 cpuMain.stack[base(cpuMain.ir.l, cpuMain.bp) + cpuMain.ir.m] = cpuMain.stack[cpuMain.sp-1];
                 cpuMain.sp--;
                 break;
            case CAL:
                 cpuMain.stack[cpuMain.sp] = base(cpuMain.ir.l, cpuMain.bp);
                 cpuMain.stack[cpuMain.sp+1] = cpuMain.bp;
                 cpuMain.stack[cpuMain.sp+2] = cpuMain.pc;
                 cpuMain.bp = cpuMain.sp+1;
                 cpuMain.pc = cpuMain.ir.m;
                 break;
            case INC:
                 cpuMain.sp += cpuMain.ir.m;
                 break;
            case JMP:
                 cpuMain.pc = cpuMain.ir.m;
                 break;
            case JPC:
                 if (cpuMain.stack[cpuMain.sp-1] == 0){
                    cpuMain.pc = cpuMain.ir.m;
                    }
                    cpuMain.sp--;
                 break;
            case WRT:
                 printf("%d\n", cpuMain.stack[cpuMain.sp-1]);
                 cpuMain.sp--;
                 break;
            case RIN:
                 cpuMain.sp++;
                 printf("Enter a value: ");
                 scanf("%d", &cpuMain.stack[cpuMain.sp-1]);
       }//end of switch statement
}//end of execute cycle

void opr(){
     switch (cpuMain.ir.m){
            case RET:
                 cpuMain.sp = cpuMain.bp-1;
                 cpuMain.pc = cpuMain.stack[cpuMain.sp+2];
                 cpuMain.bp = cpuMain.stack[cpuMain.sp+1];
                 break;
            case NEG:
                 cpuMain.stack[cpuMain.sp-1] = -cpuMain.stack[cpuMain.sp-1];
                 break;
            case ADD:
                 cpuMain.sp--;
                 cpuMain.stack[cpuMain.sp-1] += cpuMain.stack[cpuMain.sp];
                 break;
            case SUB:
                 cpuMain.sp--;
                 cpuMain.stack[cpuMain.sp-1] -= cpuMain.stack[cpuMain.sp];
                 break;
            case MUL:
                 cpuMain.sp--;
                 cpuMain.stack[cpuMain.sp-1] *= cpuMain.stack[cpuMain.sp];
                 break;
            case DIV:
                 cpuMain.sp--;
                 cpuMain.stack[cpuMain.sp-1] /= cpuMain.stack[cpuMain.sp];
                 break;
            case ODD:
                 cpuMain.stack[cpuMain.sp-1] = cpuMain.stack[cpuMain.sp-1]%2;
                 break;
            case MOD:
                 cpuMain.sp--;
                 cpuMain.stack[cpuMain.sp-1] = cpuMain.stack[cpuMain.sp-1]%cpuMain.stack[cpuMain.sp];
                 break;
            case EQL:
                 cpuMain.sp--;
                 cpuMain.stack[cpuMain.sp-1] = (cpuMain.stack[cpuMain.sp-1] == cpuMain.stack[cpuMain.sp]);
                 break;
            case NEQ:
                 cpuMain.sp--;
                 cpuMain.stack[cpuMain.sp-1] = (cpuMain.stack[cpuMain.sp-1] != cpuMain.stack[cpuMain.sp]);
                 break;
            case LSS:
                 cpuMain.sp--;
                 cpuMain.stack[cpuMain.sp-1] = (cpuMain.stack[cpuMain.sp-1] < cpuMain.stack[cpuMain.sp]);
                 break;
            case LEQ:
                 cpuMain.sp--;
                 cpuMain.stack[cpuMain.sp-1] = (cpuMain.stack[cpuMain.sp-1] <= cpuMain.stack[cpuMain.sp]);
                 break;
            case GTR:
                 cpuMain.sp--;
                 cpuMain.stack[cpuMain.sp-1] = (cpuMain.stack[cpuMain.sp-1] > cpuMain.stack[cpuMain.sp]);
                 break;
            case GEQ:
                 cpuMain.sp--;
                 cpuMain.stack[cpuMain.sp-1] = (cpuMain.stack[cpuMain.sp-1] >= cpuMain.stack[cpuMain.sp]);
                 break;
     }//end of nested switch statement
}//end of opr function

char *opToString(int op){
       switch (op){
              case 1:
                   return "lit";
              case 2:
                   return "opr";
              case 3:
                   return "lod";
              case 4:
                   return "sto";
              case 5:
                   return "cal";
              case 6:
                   return "inc";
              case 7:
                   return "jmp";
              case 8:
                   return "jpc";
              case 9:
                   return "wrt";
              case 10:
                   return "rin";
              default:
                   printf("Unrecognized opcode %d detected.\n", op);
                   printf("Program execution aborted.\n");
                   exit(1);
       }//end of switch statement
}//end of opToString function

void printStack(int n){
   int i;
   for (i=0; i<n; i++){
       printf("%d ", cpuMain.stack[i]);
   }
   printf("\n");
}//end of printStack function

int max(int a, int b){
    if (a > b)
       return a;
    else return b;
}//end of max function

int base(int l, int basepointer){
   int b1;
   b1=basepointer;
   while (l>0){
      b1=cpuMain.stack[b1];
      l--;
   }
   return b1-1;
}//end of base function
